package server;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;

import com.google.gson.GsonBuilder;

import server.exclusionstrategy.LobbyExclusionStrategy;
import server.threads.LobbyServerThread;

/**
 * Uses a Singleton to maintain the one instance of the LobbyServer running
 * Maintains a list of all GameServers running
 * User makes a connection to this server when application is launched
 * Instantiates a thread for handling the actions that exist from the lobby
 * such as creating and joining a game
 */
public class LobbyServer {
	// Default port in which this LobbyServer resides on
	public static final int SERVERSOCKET_PORT = 7999;
	
	// Return codes for joining a game
	public static final int JOIN_SUCCESSFUL = 1;
	public static final int NO_GAME_EXISTS = 0;
	public static final int GAME_FULL = -1;
	public static final int WRONG_PASSWORD = -2;
		
	// Variable to hold the one and only instance of this class
	private static LobbyServer instance;
		
	// HashMap of all the GameServers that exist
	private HashMap<String, GameServer> gameServers;
	
	/**
	 * Creates a new LobbyServer
	 */
	private LobbyServer() {
		gameServers = new HashMap<String, GameServer>();
	}
	
	/**
	 * @return the single instance of the LobbyServer
	 */
	public synchronized static LobbyServer getInstance() {
		if(instance == null) {
			instance = new LobbyServer();
		}
		return instance;
	}
	
	/**
	 * Starts the LobbyServer. The first action that must take place for the
	 * application to run.
	 */
	public void startServer() {
		ServerSocket serverSocket = null;
		
		boolean serverRunning = true;
		
		try {
			serverSocket = new ServerSocket(SERVERSOCKET_PORT);
		} catch (IOException e) {
			System.err.println("Could not listen on port: " + SERVERSOCKET_PORT);
		}
		
		// Keep listening for connections from users
		while(serverRunning) {
			try {
				Socket clientSocket = serverSocket.accept();
				
				// Once a connection is established from a user, designate
				// all tasks to LobbyServerThread. This thread handles
				// all actions that can be made from the lobby.
				GameClientInfo client = new GameClientInfo("PLAYER", clientSocket);
				LobbyServerThread thread = new LobbyServerThread(client);
				thread.start();
			} catch (IOException e) {
				System.err.println("Could not accept connection with client");
			}
		}
		
		try {
			serverSocket.close();
		} catch (IOException e) {
			System.err.println("Could not close socket");
		}
	}
	
	/**
	 * Creates a new GameServer with the name of the server provided along with the
	 * name of the player who created it (the admin), and the Socket connection of the
	 * user who created the game
	 * 
	 * @param gameName the name of the GameServer
	 * @param client the client information of the player that created the game
	 */
	public synchronized boolean createGame(String gameName, GameClientInfo client, String password, int timeLimit, int maxScore) {
		// Variables for renaming a game with the same name if needed
		int i = 1;
		String newName = gameName;
		
		// Checks if a game with the same name has already been made
		while(gameServers.get(newName) != null) {
			newName = new String(gameName + " (" + i + ")");
			i ++;
		}
		
		if(client.getName() != null) {
			// Create, add the admin, and add the game to the list of GameServers
			GameServer gameServer = new GameServer(newName, client.getName(), password, timeLimit, maxScore);
			
			gameServer.addPlayer(client);
			gameServers.put(newName, gameServer);
			
			return true;
		}
		
		// Only occurs if somehow the player has bypassed the entering of name screen
		// and is trying to create a game
		return false;
	}
	
	/**
	 * Allows a player to join the game by adding the player to the list of players
	 * in the GameServer. If the name of the game doesn't exist or if the game is 
	 * full, the player cannot join.
	 * 
	 * @param gameName the name of the game that the player to join a GameServer
	 * @param client the client information of the player to join a GameServer
	 * @return 0 if the game doesn't exist, -1 if the game is full, -2 if the
	 * password is incorrect, and 1 if the join is successful
	 */
	public synchronized int joinGame(String gameName, GameClientInfo client, String password) {
		// Obtain the corresponding GameServer object based on the gameName passed
		GameServer gameServer = gameServers.get(gameName);
		
		// See if the game actually exists
		if(gameServer == null) {
			return NO_GAME_EXISTS;
		}
		// See if the game is full
		else if(gameServer.isFull()) {
			return GAME_FULL;
		}
		// See if the game is password protected and if so, check if it is correct
		else if(!gameServer.checkPassword(password)) {
			return WRONG_PASSWORD;
		}
		else {
			// Add the user to the list of players to the GameServer that the
			// user is to join
			gameServer.addPlayer(client);
			gameServers.put(gameName, gameServer);
			
			return JOIN_SUCCESSFUL;
		}
	}
	
	/**
	 * Delete the GameServer corresponding the gameName
	 * @param gameName the name of the GameServer to be deleted
	 */
	public void deleteGame(String gameName) {
		gameServers.remove(gameName);
	}
	
	/**
	 * @return a HashMap of all the games by their name and 
	 * corresponding GameServers
	 */
	public HashMap<String, GameServer> getGameServers() {
		return gameServers;
	}
	
	/**
	 * When a player leaves a GameServer, the player will be sent back to the
	 * LobbyServer. This method creates a thread to handle messages between the
	 * user and the LobbyServer
	 * @param client the GameClientInfo containing the user's name and socket
	 */
	public void retainPlayer(GameClientInfo client) {
		LobbyServerThread thread = new LobbyServerThread(client);
		thread.start();
	}
	
	/**
	 * @return a JSON representation of all the game servers
	 */
	public String toJson() {
		GsonBuilder gson = new GsonBuilder();
		gson.setExclusionStrategies(new LobbyExclusionStrategy());
		return gson.create().toJson(gameServers.values());
	}
	
	public static void main(String[] args) {
		LobbyServer lobbyServer = new LobbyServer();
		
		System.out.println("Lobby Server started");
		lobbyServer.startServer();
	}
}
